/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package logic;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 *
 * @author Dani
 */
public class VentaArray extends Observable<VentaArrayObserver>{
    public static final String filePath = "src/logic/Ventas.txt";
    
    private static VentaArray array = new VentaArray(filePath);
    
    private ArrayList<Venta> ventas = new ArrayList<>();
    private ArrayList<Venta> searchResult = new ArrayList<>();
    
    boolean isSearchActive = false;
    
    /**
     * Carga el archivo de ventas a la clase
     * @param path 
     */
    private VentaArray(String path) {
        FileInputStream fis;
        try {
            fis = new FileInputStream(path);
            while (true) {
                String s = readLine(fis);
                if (s == null)
                    break;
                else{
                    this.ventas.add(new Venta(s));
                }
            }
            fis.close();
            this.onInventoryChange();
        } catch (FileNotFoundException e1) {
            System.out.println("Missing input files. Unable to start");
            System.exit(1);
        } catch (IOException ex) {
            System.out.println("Error within the file system. Unable to start");
            System.exit(1);
        }
    }
    /**
     * Añade una nueva venta
     * @param v 
     */
    public void addVenta(Venta v) {
        if (this.getVenta(v.getId()) != null) {
            this.notifyError("Error. Ya existe una venta con ese código.");
            return;
        }

        this.ventas.add(v);
        this.onInventoryChange();
    }
    /**
     * Intenta guardar los datos actuales
     * @param path
     * @return true si se pudo guardar, false si no
     */
    public boolean requestSave(String path) {
        FileOutputStream fos;
        try {
            fos = new FileOutputStream(path);
        } catch (FileNotFoundException ex) {
            return false;
        }
        for (Venta v: this.ventas) {
            if (!writeLine(fos,v.toString()))
                return false;
        }
        try {
            fos.close();
        } catch (IOException ex) {
            return false;
        }
        return true;
    }
    /**
     * Devuelve la única instancia de esta clase que existe
     * en cualquier momento (singleton)
     * @return 
     */
    public static VentaArray getArray() {
        return VentaArray.array;
    }
    /**
     * Devuelve el número de ventas
     * @return 
     */
    public int getSize() {
        return this.ventas.size();
    }
    /**
     * Notifica a los observadores de que ocurrió un error
     * @param error 
     */
    public void notifyError (String error){
        for (VentaArrayObserver o : this.observerList)
            o.errorOccured(error);
    }
    
    /*
     Auxiliar loader and saver methods
     */
    private static boolean writeLine(OutputStream oStream, String toWrite) {
        for (int i = 0; i < toWrite.length(); i++) {
            try {
                oStream.write((int)toWrite.charAt(i));
            } catch (IOException ex) {
                return false;
            }
        }
        try {
            oStream.write((int)'\n');
        } catch (IOException ex) {
            return false;
        }
        return true;
    }
    private static String readLine(InputStream iOStream) {
        String s = "";
        while (true) {
            char c;
            try {
                c = readChar(iOStream);
            } catch (IOException ex) {
                if (s.equals(""))
                    return null;
                else
                    break;
            }
            if (c == '\n')
                break;
            else
                s += c;
        }
        while (true) {
            if (s.length() == 0)
                return null;
            char c = s.charAt(s.length()-1);
            if (c == '\r' || c == '\n' || c == ' ')
                s = s.substring(0, s.length()-1);
            else
                break;
             
        }       
        return s;
    }
    private static char readChar(InputStream iOStream) throws IOException {
        int result = iOStream.read();
        if (result == -1)
                throw new IOException("End of file reached");
        return (char) result;
    }
    /**
     * Fuerza una actualización de los observadores
     */
    public void forceUpdate() {
        this.onInventoryChange();
    }
    /**
     * Llamada cuando cambia el inventario.Avisa a los observers
     */
    private void onInventoryChange() {
        List<VentaInterface> updateable = new ArrayList<>();
        if (this.isSearchActive) {
            updateable.addAll(this.searchResult);
            this.isSearchActive = false;
        } else {
            updateable.addAll(this.ventas);
        }
        for (VentaArrayObserver obs : this.observerList)
            obs.inventoryChange(updateable);
    }
    /**
     * Llamada cuando se genera un vale. Avisa a los observers
     * @param v 
     */
    private void onValeGenerado(Vale v) {
        for (VentaArrayObserver obs : this.observerList)
            obs.newValeGenerado(v);
    }
    /**
     * Llamada cuando se puede cerrar el programa. Avisa a los observers
     */
    private void onQuitAvailable() {
        for (VentaArrayObserver obs : this.observerList)
            obs.quitAvailable();
    }
    /**
     * Llamada al generarse una factura. Avisa a los observers
     * @param f 
     */
    private void onNewFactura(Factura f) {
        for (VentaArrayObserver obs : this.observerList)
            obs.newFacturaGenerada(f);
    }
    
    /**
     * Realiza una busqueda de las ventas cuyos datos contienen a los enviados.
     * El resultado será enviado a través de inventoryChange
     * @param cliente
     * @param prenda
     * @param empleado
     * @param fecha 
     */
    public void performSearch(String cliente, String prenda, String empleado, Date fecha) {
        this.searchResult = new ArrayList<>();
        
        // Las comparaciones se harán con los nombres en minúscula
        empleado = empleado.toLowerCase();
        cliente = cliente.toLowerCase();
        prenda = prenda.toLowerCase();
        
        // Se comprueban los parámetros que se usarán en la búsqueda
        boolean conVendedor = empleado != null && !empleado.isEmpty();
        boolean conCliente = cliente != null && !cliente.isEmpty();
        boolean conPrenda = prenda != null && !prenda.isEmpty();
        boolean conFecha = fecha != null;
        
        // Parámetros que confirman que el objeto concuerda en ese parámetro con la búsqueda.
        boolean vendedorOk, clienteOk, prendaOk, fechaOk;
        
        // Si uno de los parámetros está activo se realizará la búsqueda
        if (conVendedor || conCliente || conPrenda || conFecha){
            for (Venta v : ventas) {
                vendedorOk = !conVendedor || v.getEmpleado().toLowerCase().contains(empleado);       
                clienteOk = !conCliente || v.getCliente().toLowerCase().contains(cliente);
                prendaOk = !conPrenda || v.buscarPrenda(prenda) >= 0;
                fechaOk = !conFecha || v.in24HourInterval(fecha);
                if (vendedorOk && clienteOk && prendaOk && fechaOk)
                    this.searchResult.add(v);
            }
            this.isSearchActive = true;
        }
        // Si ninguno de los campos tiene nada escrito no realizamos la búsqueda
        else
            this.isSearchActive = false;
        // Notifiamos que se ha producido una búsqueda o se ha desactivado la búsqueda
        this.onInventoryChange();
    }
    /**
     * Devuelve la venta correspondiente con id
     * @param id
     * @return 
     */
    public Venta getVenta (int id) {
        for (Venta v: this.ventas)
            if (v.getId() == id)
                return v;
        return null;
    }
    /**
     * Descuenta de la venta "id" la cantidad parseInt
     * @param parseInt
     * @param id 
     */
    void descuenta(int parseInt, int id) {
        Vale v = this.getVenta(id).descontar(parseInt);
        if (v != null)
            this.onValeGenerado(v);
        this.onInventoryChange();
    }
    /**
     * Llamada para pedir una salida del programa
     */
    void requestQuit() {
        this.onQuitAvailable();
    }
    /**
     * Crea una factura para la venta id
     * @param id 
     */
    void factura(int id) {
        Venta v = this.getVenta(id);
        this.onNewFactura(v.generarFactura());
    }
    
}

